#include <iostream>
#include <cstdio>
#include <stdio.h>
#include <cstring>
#include <string>
#include <cstdlib>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

using namespace std;

const int basesize = 1024;
const int gargvnum = 64;
const int envnum = 64;

//全局命令行参数表
char* gargv[gargvnum];
int gargc = 0;

//全局的当前shell的工作路径
char pwd[basesize];
char pwdenv[basesize];

//我的系统环境变量
char *genv[envnum];

string GetUserName()
{
    string name = getenv("USER");
    return name.empty() ? "None" : name;
}

string GetHostName()
{
    string hostname = getenv("HOSTNAME");
    return hostname.empty() ? "None" : hostname;
}

string GetPwd()
{
    if(nullptr == getcwd(pwd,sizeof(pwd))) return "None";
    snprintf(pwdenv,sizeof(pwdenv),"PWD=%s",pwd);
    putenv(pwdenv);//修改进程的环境列表
    return pwd;

    //string pwd = getenv("PWD");
    //return pwd.empty() ? "None" : pwd;
}

string MakeCommandLine()
{
    //[wks@hcss-ecs-ab43 lesson4]$
    char command_line[basesize];
    snprintf(command_line,sizeof(command_line),"[%s@%s %s]# ",
            GetUserName().c_str(),GetHostName().c_str(),GetPwd().c_str());

    return command_line;
}
void PrintCommandLine()//1.命令行提示符
{
    printf("%s",MakeCommandLine().c_str());
}

bool GetCommandLine(char command_buffer[] , int size)//获取用户指令
{
    //我们认为：我们要将用户的指令，当成一个字符串
    //"ls -a -l -n"
    char* result = fgets(command_buffer,size,stdin);
    if(!result)
    {
        return false;
    }

    command_buffer[strlen(command_buffer)-1] = 0;
    if(strlen(command_buffer) == 0)
    {
        return false;
    }

    return true;
}

void ParseCommandLine(char command_buffer[],int len)//3,分析指令
{
    (void)len;
    memset(gargv,0,sizeof(gargv));
    gargc = 0;
    //"ls -a -l -n"
    const char* sep = " ";
    gargv[gargc++] = strtok(command_buffer,sep);
    //= 是可以写的
    while((bool)(gargv[gargc++] = strtok(nullptr,sep)));
    gargc--;
}

//在shell中
//有些命令，必须由子进程来执行
//有些命令不能由子进程来执行，要有shell自己来执行 --- 内建命令，built GetCommandLine
bool ExecuteCommand() //4,执行命令
{
    pid_t id = fork();
    if(id < 0) return false;
    if(id == 0)
    {
        //让子进程去执行
        //1,子进程执行命令
        execvp(gargv[0],gargv);
        //2,退出
        exit(1);
    }

    int status = 0;
    pid_t rid = waitpid(id , &status, 0);
    if(rid > 0)
    {
        //等待成功
        return true;
    }
    return false;
}

void AddEnv(const char *item)
{
    int index = 0;
    while(genv[index])
    {
        index++;
    }

    //genv[index++] = item;
    //genv[index] = nullptr;//这里注意小细节，item也就是gargv[1]
    //是在缓冲区中的，当再次循环时，GetCommandLine时，缓冲区的指针刷新，item也会发生改变
    genv[index] = (char*)malloc(strlen(item) + 1);
    strncpy(genv[index] , item, strlen(item) + 1);
    genv[++index] = nullptr;
}

//shell自己执行命令，本质是shell调用自己的函数
bool CheckAndExecBuiltCommand()
{
    if(strcmp(gargv[0],"cd") == 0)
    {
        //内建命令
        if(gargc == 2)
        {
            chdir(gargv[1]);
        }
        return true;
    }
    else if(strcmp(gargv[0],"export") == 0)
    {
        //内建命令
        if(gargc == 2)
        {
            AddEnv(gargv[1]);
        }
        return true;
    }
    else if(strcmp(gargv[0],"env") == 0)
    {
        for(int i = 0; genv[i] ; i++)
        {
            printf("%s\n",genv[i]);
        }
        return true;
    }

    return false;
}



void debug()
{
    printf("argc: %d\n",gargc);
    for(int i = 0; gargv[i]; i++)
    {
        printf("argv[%d]: %s\n",i,gargv[i]);
    }
}

//作为一个shell，获取环境变量应该从系统的配置来
//我们今天从父shell中获取环境变量
void InitEnv()
{
    extern char** environ;//指向字符指针的指针数组
    int index = 0;
    while(environ[index])
    {
        genv[index] = (char*)malloc(strlen(environ[index]) + 1);
        strncpy(genv[index],environ[index],strlen(environ[index])+1);
        index++;
    }
    genv[index] = nullptr;
}

int main()
{
    InitEnv();
    char command_buffer[basesize];
    while(true)
    {
        PrintCommandLine(); //1,命令行提示符
        //command_buffer ->output
        if(!GetCommandLine(command_buffer,basesize))//获取用户命令
        {
            continue;
        }
        printf("%s\n",command_buffer);
        //ls
        //"ls -a -b -c -d" -> "ls" "-a" "-b" "-d"
        ParseCommandLine(command_buffer,strlen(command_buffer));//3,分析指令
        
        if(CheckAndExecBuiltCommand())
        {
            continue;
        }

        //debug();
        ExecuteCommand(); //4,执行命令
    }

    return 0;
}
